#!/usr/bin/env python3
"""
Generic LLM Connection Manager for Fortran Code Commentator

This enhanced connection manager provides robust integration with Large Language Model APIs
including Ollama, OpenAI, and other compatible services. Features include:

- Connection pooling for high-performance concurrent requests
- Health monitoring with automatic degradation strategies
- Git diff format validation for code commentary tasks
- Adaptive prompting based on connection quality
- Comprehensive error handling and retry logic
- Response validation for structured Fortran comments

Originally adapted from GSI/EnKF Automated Code Commentary System.
Supports specialized use cases for scientific Fortran codebase documentation.

Author: GSI Development Team / Fortran Code Commentator
"""

import json
import logging
import re
import requests
import threading
import time
from pathlib import Path
from typing import Dict, List, Optional, Tuple, Any
from dataclasses import dataclass

logger = logging.getLogger(__name__)


@dataclass
class ConnectionHealth:
    """Track connection health and statistics"""
    is_connected: bool = False
    is_degraded: bool = False
    total_requests: int = 0
    successful_requests: int = 0
    consecutive_failures: int = 0
    last_success: float = 0.0
    last_failure: float = 0.0
    average_response_time: float = 0.0
    current_load: int = 0  # Number of concurrent requests


@dataclass
class ModelCapabilities:
    """Track model capabilities and limitations"""
    max_tokens: int = 4096
    supports_diff: bool = True
    supports_streaming: bool = False
    context_window: int = 8192
    temperature_range: Tuple[float, float] = (0.0, 1.0)
    max_concurrent_requests: int = 4
    estimated_tokens_per_second: float = 50.0


class LLMConnectionManager:
    """
    Generic LLM connection manager with robust error handling and graceful degradation
    
    Supports multiple LLM providers including:
    - Ollama (default): Local model deployment
    - OpenAI: API-based service (future enhancement)
    - Other compatible REST APIs
    
    Optimized for code commentary tasks with git diff format validation.
    """
    
    def __init__(self, base_url: str = "http://60.245.128.27:11434", 
                 model_name: str = "hopephoto/qwen3-30b-a3b-instruct_q8:latest",
                 max_concurrent_connections: int = 4,
                 connection_timeout: int = 30,
                 read_timeout: int = 300,
                 llm_logger=None):
        self.base_url = base_url
        self.model_name = model_name
        self.max_concurrent_connections = max_concurrent_connections
        self.connection_timeout = connection_timeout
        self.read_timeout = read_timeout
        
        # LLM operations logger
        self.llm_logger = llm_logger
        
        # Connection pool management
        self.session_pool = []
        self.session_lock = threading.Lock()
        self._initialize_session_pool()
        
        # Health monitoring
        self.health = ConnectionHealth()
        self.health_lock = threading.Lock()
        
        # Model capabilities
        self.capabilities = ModelCapabilities()
        
        # Graceful degradation settings
        self.degraded_mode = False
        self.fallback_strategies = ['retry', 'timeout_increase', 'simplified_prompt']
        self.current_strategy_index = 0
        
        # Response validation patterns
        self.diff_validation_patterns = {
            'header_pattern': re.compile(r'^---\s+a/.+\n\+\+\+\s+b/.+', re.MULTILINE),
            'hunk_pattern': re.compile(r'^@@\s+-\d+,\d+\s+\+\d+,\d+\s+@@', re.MULTILINE),
            'diff_content_pattern': re.compile(r'^[+\-\s].*$', re.MULTILINE)
        }
        
    def _log_llm_operation(self, operation: str, file_path: str, details: str = "", success: bool = True):
        """Log LLM operation details if logger is available"""
        if self.llm_logger:
            status = "SUCCESS" if success else "FAILED"
            self.llm_logger.info(f"{operation} | {status} | {file_path} | {details}")
    
    def _clean_markdown_wrapper(self, content: str) -> str:
        """Remove markdown code blocks and other wrappers from LLM response"""
        content = content.strip()
        lines = content.split('\n')
        
        # Remove markdown code block wrappers (more flexible)
        # Handle cases like ```diff, ```, or ```fortran
        start_idx = 0
        end_idx = len(lines)
        
        # Find start of content (skip any line starting with ```)
        for i, line in enumerate(lines):
            if line.strip().startswith('```'):
                start_idx = i + 1
                break
        
        # Find end of content (skip any line ending with ```)
        for i in range(len(lines) - 1, -1, -1):
            if lines[i].strip().endswith('```') or lines[i].strip() == '```':
                end_idx = i
                break
        
        # Extract content between markers
        if start_idx < end_idx:
            lines = lines[start_idx:end_idx]
        
        # Find the actual diff start (--- line) - more robust
        diff_start = -1
        for i, line in enumerate(lines):
            stripped = line.strip()
            if (stripped.startswith('--- a/') or 
                stripped.startswith('--- ') or
                (stripped.startswith('---') and len(stripped.split()) >= 2)):
                diff_start = i
                break
        
        # If we found a diff start, use content from there
        if diff_start >= 0:
            lines = lines[diff_start:]
        
        # Clean up the lines - remove any non-diff content at the end
        cleaned_lines = []
        for line in lines:
            stripped = line.strip()
            
            # Skip empty lines at start
            if not cleaned_lines and not stripped:
                continue
                
            # Stop at any explanatory text or non-diff content
            if (stripped and 
                not line.startswith(('---', '+++', '@@', ' ', '+', '-')) and
                not stripped.startswith(('---', '+++', '@@', ' ', '+', '-'))):
                # This looks like explanatory text, stop here
                break
                
            cleaned_lines.append(line)
        
        # Remove trailing empty lines
        while cleaned_lines and not cleaned_lines[-1].strip():
            cleaned_lines.pop()
        
        return '\n'.join(cleaned_lines)
    
    def _log_detailed_response(self, content: str, file_path: str, attempt: int, prefix: str = "RAW"):
        """Log the detailed LLM response content for debugging"""
        if not self.llm_logger:
            return
            
        # Log a separator and header
        self.llm_logger.info(f"=== {prefix} LLM RESPONSE - {file_path} - Attempt {attempt} ===")
        
        # Log the content with line numbers for easier analysis
        lines = content.split('\n')
        for i, line in enumerate(lines[:50], 1):  # Limit to first 50 lines to avoid huge logs
            # Show line prefixes clearly for diff analysis
            prefix_char = line[0] if line else '(empty)'
            self.llm_logger.info(f"L{i:02d}: [{prefix_char}] {repr(line)}")
        
        if len(lines) > 50:
            self.llm_logger.info(f"... truncated {len(lines) - 50} more lines")
            
        self.llm_logger.info(f"=== END {prefix} RESPONSE - Total {len(lines)} lines ===\n")
        
    def _initialize_session_pool(self):
        """Initialize connection pool with multiple sessions"""
        with self.session_lock:
            for _ in range(self.max_concurrent_connections):
                session = requests.Session()
                session.timeout = (self.connection_timeout, self.read_timeout)
                # Configure session for better performance
                session.headers.update({
                    'Content-Type': 'application/json',
                    'Connection': 'keep-alive',
                    'Accept': 'application/json'
                })
                self.session_pool.append(session)
                
    def _get_session(self) -> requests.Session:
        """Get available session from pool"""
        with self.session_lock:
            if self.session_pool:
                return self.session_pool.pop()
            else:
                # Create temporary session if pool is empty
                session = requests.Session()
                session.timeout = (self.connection_timeout, self.read_timeout)
                return session
                
    def _return_session(self, session: requests.Session):
        """Return session to pool"""
        with self.session_lock:
            if len(self.session_pool) < self.max_concurrent_connections:
                self.session_pool.append(session)
                
    def _update_health_metrics(self, success: bool, response_time: float = 0.0):
        """Update connection health metrics"""
        with self.health_lock:
            current_time = time.time()
            self.health.total_requests += 1
            
            if success:
                self.health.successful_requests += 1
                self.health.last_success = current_time
                self.health.consecutive_failures = 0
                self.health.is_connected = True
                
                # Update average response time
                if self.health.average_response_time == 0:
                    self.health.average_response_time = response_time
                else:
                    self.health.average_response_time = (
                        self.health.average_response_time * 0.8 + response_time * 0.2
                    )
            else:
                self.health.consecutive_failures += 1
                self.health.last_failure = current_time
                
                # Mark as disconnected after 3 consecutive failures
                if self.health.consecutive_failures >= 3:
                    self.health.is_connected = False
                    
    def test_connection(self) -> bool:
        """Enhanced connection test with health monitoring"""
        session = self._get_session()
        start_time = time.time()
        
        try:
            response = session.get(f"{self.base_url}/api/tags", timeout=10)
            response.raise_for_status()
            
            response_time = time.time() - start_time
            
            # Validate response structure
            try:
                data = response.json()
                models = data.get('models', [])
                model_names = [model.get('name', '') for model in models]
                is_model_available = self.model_name in model_names
                
                if is_model_available:
                    self._update_health_metrics(True, response_time)
                    logger.info(f"Connection test successful. Model '{self.model_name}' is available.")
                    return True
                else:
                    logger.warning(f"Model '{self.model_name}' not found in available models: {model_names}")
                    self._update_health_metrics(False)
                    return False
                    
            except (ValueError, KeyError) as e:
                logger.error(f"Invalid response format from LLM API: {e}")
                self._update_health_metrics(False)
                return False
                
        except requests.exceptions.Timeout:
            logger.warning(f"Connection test timeout after {time.time() - start_time:.2f}s")
            self._update_health_metrics(False)
            return False
            
        except requests.exceptions.ConnectionError as e:
            logger.error(f"Connection test failed - server unavailable: {e}")
            self._update_health_metrics(False)
            return False
            
        except requests.exceptions.HTTPError as e:
            logger.error(f"Connection test failed - HTTP error: {e}")
            self._update_health_metrics(False)
            return False
            
        except Exception as e:
            logger.error(f"Connection test failed - unexpected error: {e}")
            self._update_health_metrics(False)
            return False
        finally:
            self._return_session(session)
    
    def _validate_diff_response(self, response_content: str, file_path: str) -> Tuple[bool, List[str]]:
        """Validate generated diff response"""
        issues = []
        
        if not response_content or not response_content.strip():
            issues.append("Empty response from model")
            return False, issues
            
        # Check for diff headers
        if not self.diff_validation_patterns['header_pattern'].search(response_content):
            issues.append("Missing or invalid diff headers (--- a/file, +++ b/file)")
            
        # Check for hunk headers
        if not self.diff_validation_patterns['hunk_pattern'].search(response_content):
            issues.append("Missing or invalid hunk headers (@@ -old_start,old_count +new_start,new_count @@)")
            
        # Check for diff content (lines starting with +, -, or space)
        lines = response_content.split('\n')
        diff_lines = [line for line in lines if line and not line.startswith(('---', '+++', '@@'))]
        
        if diff_lines:
            invalid_lines = [line for line in diff_lines 
                           if not re.match(r'^[+\-\s].*$', line)]
            if invalid_lines:
                issues.append(f"Invalid diff content lines: {len(invalid_lines)} lines don't start with +, -, or space")
        
        # Check for structured comments in added lines
        added_lines = [line[1:] for line in diff_lines if line.startswith('+')]
        comment_indicators = ['!>', '@brief', '@details', '@param', '@return', '@usage']
        has_structured_comments = any(
            any(indicator in line for indicator in comment_indicators) 
            for line in added_lines
        )
        
        if not has_structured_comments:
            issues.append("Generated diff appears to lack structured comments (@brief, @details, etc.)")
            
        is_valid = len(issues) == 0
        return is_valid, issues
        
    def _apply_graceful_degradation(self, attempt: int) -> Dict[str, Any]:
        """Apply graceful degradation strategies based on connection health"""
        strategy_config = {
            'timeout_multiplier': 1.0,
            'temperature': 0.3,
            'max_tokens': 2048,
            'simplified_prompt': False
        }
        
        if self.health.is_degraded or attempt > 1:
            # Increase timeouts progressively
            strategy_config['timeout_multiplier'] = 1.5 ** attempt
            
            # Reduce model creativity for more reliable output
            strategy_config['temperature'] = max(0.1, 0.3 - (attempt * 0.05))
            
            # Reduce token limit to avoid timeouts
            strategy_config['max_tokens'] = max(1024, 2048 - (attempt * 256))
            
            if attempt >= 2:
                strategy_config['simplified_prompt'] = True
                
        return strategy_config
        
    def _get_adaptive_prompt(self, base_prompt: str, file_path: str, fortran_code: str, 
                           simplified: bool = False) -> str:
        """Generate adaptive prompt based on connection state"""
        filename = Path(file_path).name
        
        if simplified:
            # Simplified prompt for degraded connections
            return f"""{base_prompt}

Add structured comments to this Fortran code. Generate a unified diff patch.

File: {filename}
Code:
{fortran_code[:1000]}{'...' if len(fortran_code) > 1000 else ''}

Return ONLY the diff patch with:
--- a/{filename}
+++ b/{filename}
@@ ... @@
+!> @brief Brief description
 existing code"""
        else:
            # Full detailed prompt for good connections
            return f"""{base_prompt}

CRITICAL: You must generate a valid unified diff format patch that adds comments to the Fortran code.

Original file path: {file_path}

Original Fortran code:
```fortran
{fortran_code}
```

Generate ONLY a unified diff patch in this exact format:
```diff
--- a/{filename}
+++ b/{filename}
@@ -start,count +start,count @@
 context lines (unchanged)
-lines to remove (if any)
+lines to add (comments and modified lines)
 more context lines
```

Requirements:
1. Use exact unified diff format with proper headers
2. Add structured comments (@brief, @details, @param, etc.) 
3. Preserve all original Fortran code exactly
4. Include sufficient context lines around changes
5. Ensure line numbers are accurate
6. Return ONLY the diff patch, no explanations"""
    
    def generate_diff_patch(self, prompt: str, fortran_code: str, file_path: str,
                           max_retries: int = 1) -> Optional[str]:
        """Enhanced diff patch generation with robust error handling and graceful degradation"""
        
        # Pre-flight check
        if not self.health.is_connected and not self.test_connection():
            logger.warning("Model unavailable - cannot generate diff patch")
            return None
            
        # Track current load
        with self.health_lock:
            self.health.current_load += 1
            
        session = None
        try:
            for attempt in range(max_retries):
                start_time = time.time()
                session = self._get_session()
                
                try:
                    # Apply graceful degradation
                    degradation_config = self._apply_graceful_degradation(attempt)
                    
                    # Generate adaptive prompt
                    full_prompt = self._get_adaptive_prompt(
                        prompt, file_path, fortran_code, 
                        degradation_config['simplified_prompt']
                    )
                    
                    payload = {
                        "model": self.model_name,
                        "prompt": full_prompt,
                        "stream": False,
                        "options": {
                            "temperature": degradation_config['temperature'],
                            "top_p": 0.9,
                            "max_tokens": degradation_config['max_tokens']
                        }
                    }
                    
                    # Apply adaptive timeout
                    request_timeout = (
                        self.connection_timeout * degradation_config['timeout_multiplier'],
                        self.read_timeout * degradation_config['timeout_multiplier']
                    )
                    
                    logger.info(f"Generating diff patch (attempt {attempt + 1}/{max_retries}) "
                              f"for {Path(file_path).name}, timeout: {request_timeout[1]:.1f}s")
                    
                    # Log the LLM request
                    self._log_llm_operation("DIFF_REQUEST", Path(file_path).name, 
                                          f"Attempt {attempt + 1}/{max_retries}, timeout: {request_timeout[1]:.1f}s", True)
                    
                    response = session.post(
                        f"{self.base_url}/api/generate", 
                        json=payload,
                        timeout=request_timeout
                    )
                    response.raise_for_status()
                    
                    response_time = time.time() - start_time
                    
                    result = response.json()
                    if 'response' not in result:
                        logger.warning(f"No response field in result: {result}")
                        self._update_health_metrics(False)
                        continue
                        
                    generated_content = result['response'].strip()
                    
                    # Log the raw response before cleaning
                    self._log_llm_operation("RAW_RESPONSE", Path(file_path).name, 
                                          f"Attempt {attempt + 1}, length: {len(generated_content)} chars", True)
                    self._log_detailed_response(generated_content, Path(file_path).name, attempt + 1)
                    
                    # Clean markdown code blocks if present
                    generated_content = self._clean_markdown_wrapper(generated_content)
                    
                    # Log the cleaned response
                    self._log_llm_operation("CLEANED_RESPONSE", Path(file_path).name, 
                                          f"Attempt {attempt + 1}, length: {len(generated_content)} chars", True)
                    self._log_detailed_response(generated_content, Path(file_path).name, attempt + 1, prefix="CLEANED")
                    
                    # Validate response
                    is_valid, validation_issues = self._validate_diff_response(
                        generated_content, file_path
                    )
                    
                    if is_valid:
                        self._update_health_metrics(True, response_time)
                        logger.info(f"Successfully generated diff patch in {response_time:.2f}s")
                        self._log_llm_operation("DIFF_SUCCESS", Path(file_path).name, 
                                              f"Generated in {response_time:.2f}s, attempt {attempt + 1}", True)
                        return generated_content
                    else:
                        logger.warning(f"Generated diff failed validation: {validation_issues}")
                        self._log_llm_operation("DIFF_VALIDATION_FAILED", Path(file_path).name, 
                                              f"Attempt {attempt + 1}: {'; '.join(validation_issues)}", False)
                        if attempt < max_retries - 1:
                            logger.info("Retrying with validation feedback...")
                            continue
                            
                except requests.exceptions.Timeout:
                    response_time = time.time() - start_time
                    logger.warning(f"Request timeout after {response_time:.2f}s on attempt {attempt + 1}")
                    self._log_llm_operation("DIFF_TIMEOUT", Path(file_path).name, 
                                          f"Timeout after {response_time:.2f}s, attempt {attempt + 1}", False)
                    self._update_health_metrics(False)
                    
                    if attempt < max_retries - 1:
                        backoff_time = min(60, 2 ** attempt)
                        logger.info(f"Backing off for {backoff_time}s...")
                        time.sleep(backoff_time)
                        
                except requests.exceptions.ConnectionError as e:
                    logger.error(f"Connection error on attempt {attempt + 1}: {e}")
                    self._log_llm_operation("DIFF_CONNECTION_ERROR", Path(file_path).name, 
                                          f"Connection error on attempt {attempt + 1}: {str(e)[:100]}", False)
                    self._update_health_metrics(False)
                    
                    # Test connection and potentially trigger degraded mode
                    if not self.test_connection():
                        self.degraded_mode = True
                        logger.warning("Entering degraded mode due to connection issues")
                        
                    if attempt < max_retries - 1:
                        backoff_time = min(60, 3 ** attempt)
                        logger.info(f"Connection recovery backoff: {backoff_time}s...")
                        time.sleep(backoff_time)
                        
                except requests.exceptions.HTTPError as e:
                    logger.error(f"HTTP error on attempt {attempt + 1}: {e}")
                    self._update_health_metrics(False)
                    
                    if e.response.status_code in [503, 429]:  # Service unavailable or rate limited
                        if attempt < max_retries - 1:
                            backoff_time = min(120, 5 ** attempt)
                            logger.info(f"Server overload backoff: {backoff_time}s...")
                            time.sleep(backoff_time)
                    else:
                        # For other HTTP errors, don't retry
                        break
                        
                except Exception as e:
                    logger.error(f"Unexpected error on attempt {attempt + 1}: {e}")
                    self._update_health_metrics(False)
                    
                    if attempt < max_retries - 1:
                        backoff_time = min(30, 2 ** attempt)
                        time.sleep(backoff_time)
                finally:
                    if session:
                        self._return_session(session)
                        session = None
                        
            # All attempts failed
            logger.error(f"Failed to generate diff patch after {max_retries} attempts")
            return None
            
        finally:
            # Clean up load tracking
            with self.health_lock:
                self.health.current_load = max(0, self.health.current_load - 1)
            
            if session:
                self._return_session(session)
    
    def generate_comments(self, prompt: str, file_path: str, fortran_code: str, 
                         max_attempts: int = 3) -> Optional[str]:
        """Backwards compatibility wrapper for generate_diff_patch"""
        return self.generate_diff_patch(prompt, fortran_code, file_path, max_attempts)
    
    def get_health_report(self) -> Dict[str, Any]:
        """Get comprehensive health report"""
        with self.health_lock:
            success_rate = 0.0
            if self.health.total_requests > 0:
                success_rate = self.health.successful_requests / self.health.total_requests
                
            return {
                "connection_status": {
                    "is_connected": self.health.is_connected,
                    "is_degraded": self.health.is_degraded,
                    "consecutive_failures": self.health.consecutive_failures
                },
                "performance_metrics": {
                    "total_requests": self.health.total_requests,
                    "successful_requests": self.health.successful_requests,
                    "success_rate": success_rate,
                    "average_response_time": self.health.average_response_time,
                    "current_load": self.health.current_load
                },
                "model_info": {
                    "name": self.model_name,
                    "base_url": self.base_url,
                    "max_tokens": self.capabilities.max_tokens,
                    "context_window": self.capabilities.context_window
                },
                "timestamps": {
                    "last_success": self.health.last_success,
                    "last_failure": self.health.last_failure
                }
            }
    
    def get_health_status(self) -> Dict[str, Any]:
        """Get current health status and statistics"""
        with self.health_lock:
            return {
                'is_connected': self.health.is_connected,
                'is_degraded': self.health.is_degraded,
                'success_rate': self.health.successful_requests / max(1, self.health.total_requests),
                'consecutive_failures': self.health.consecutive_failures,
                'total_requests': self.health.total_requests,
                'successful_requests': self.health.successful_requests,
                'average_response_time': self.health.average_response_time,
                'current_load': self.health.current_load,
                'last_success': self.health.last_success,
                'last_failure': self.health.last_failure,
                'degraded_mode': self.degraded_mode
            }
    
    def reset_health_metrics(self):
        """Reset health metrics - useful for testing or recovery"""
        with self.health_lock:
            self.health = ConnectionHealth()
            self.degraded_mode = False
            logger.info("Health metrics reset")
    
    def _analyze_fortran_structure(self, fortran_code: str) -> Tuple[List[Dict], List[Dict], List[Dict]]:
        """Analyze Fortran code structure to identify key elements for commenting"""
        lines = fortran_code.split('\n')
        subroutines = []
        functions = []
        modules = []
        
        # Patterns for identifying Fortran constructs
        subroutine_pattern = re.compile(r'^\s*subroutine\s+(\w+)\s*\(', re.IGNORECASE)
        function_pattern = re.compile(r'^\s*(integer|real|logical|character|type)\s+function\s+(\w+)\s*\(', re.IGNORECASE)
        function_pattern2 = re.compile(r'^\s*function\s+(\w+)\s*\(', re.IGNORECASE)
        module_pattern = re.compile(r'^\s*module\s+(\w+)', re.IGNORECASE)
        
        for i, line in enumerate(lines, 1):
            # Check for subroutines
            sub_match = subroutine_pattern.search(line)
            if sub_match:
                subroutines.append({
                    'name': sub_match.group(1),
                    'line': i,
                    'text': line.strip()
                })
            
            # Check for functions
            func_match = function_pattern.search(line) or function_pattern2.search(line)
            if func_match:
                # Get function name from appropriate group
                name = func_match.group(2) if func_match.lastindex >= 2 else func_match.group(1)
                functions.append({
                    'name': name,
                    'line': i,
                    'text': line.strip()
                })
            
            # Check for modules
            mod_match = module_pattern.search(line)
            if mod_match:
                modules.append({
                    'name': mod_match.group(1),
                    'line': i,
                    'text': line.strip()
                })
                
        return subroutines, functions, modules
    
    def _get_fortran_analysis_summary(self, fortran_code: str) -> str:
        """Generate summary of Fortran code structure for prompting"""
        subroutines, functions, modules = self._analyze_fortran_structure(fortran_code)
        
        info_parts = []
        
        if modules:
            info_parts.append("Modules found:")
            for mod in modules:
                info_parts.append(f"  - {mod['name']} (line {mod['line']})")
                
        if subroutines:
            info_parts.append("Subroutines found:")
            for sub in subroutines[:5]:  # Limit to first 5 for prompt space
                info_parts.append(f"  - {sub['name']} (line {sub['line']})")
            if len(subroutines) > 5:
                info_parts.append(f"  ... and {len(subroutines) - 5} more subroutines")
        
        if functions:
            info_parts.append("Functions found:")
            for func in functions[:5]:  # Limit to first 5 for prompt space  
                info_parts.append(f"  - {func['name']} (line {func['line']})")
            if len(functions) > 5:
                info_parts.append(f"  ... and {len(functions) - 5} more functions")
        
        if not (modules or subroutines or functions):
            info_parts.append("No clear subroutines, functions, or modules detected.")
            info_parts.append("Focus on adding file-level documentation and any identifiable code blocks.")
        
        return "\n".join(info_parts)
    
    @classmethod
    def create_ollama_manager(cls, base_url: str = "http://60.245.128.27:11434", 
                             model_name: str = "hopephoto/qwen3-30b-a3b-instruct_q8:latest",
                             max_connections: int = 4) -> 'LLMConnectionManager':
        """
        Factory method to create an LLM manager configured for Ollama
        
        Args:
            base_url: Ollama server URL
            model_name: Model name as registered in Ollama
            max_connections: Maximum concurrent connections
            
        Returns:
            Configured LLMConnectionManager instance
        """
        return cls(
            base_url=base_url,
            model_name=model_name,
            max_concurrent_connections=max_connections,
            connection_timeout=30,
            read_timeout=300
        )
    
    @classmethod
    def create_openai_manager(cls, api_key: str, model_name: str = "gpt-3.5-turbo",
                             max_connections: int = 4) -> 'LLMConnectionManager':
        """
        Factory method to create an LLM manager configured for OpenAI (future enhancement)
        
        Args:
            api_key: OpenAI API key
            model_name: OpenAI model name
            max_connections: Maximum concurrent connections
            
        Returns:
            Configured LLMConnectionManager instance
            
        Note:
            This is a placeholder for future OpenAI integration.
            Currently only Ollama-compatible endpoints are supported.
        """
        # Future enhancement: implement OpenAI API integration
        raise NotImplementedError("OpenAI integration not yet implemented")
    
    def configure_for_provider(self, provider: str, **kwargs):
        """
        Configure the manager for specific LLM providers
        
        Args:
            provider: Provider name ('ollama', 'openai', 'custom')
            **kwargs: Provider-specific configuration options
        """
        if provider.lower() == 'ollama':
            # Update for Ollama-specific optimizations
            self.capabilities.max_tokens = kwargs.get('max_tokens', 4096)
            self.capabilities.context_window = kwargs.get('context_window', 8192)
            self.capabilities.supports_streaming = True
        elif provider.lower() == 'openai':
            # Future: OpenAI-specific configurations
            raise NotImplementedError("OpenAI provider not yet supported")
        else:
            logger.warning(f"Unknown provider '{provider}', using default configuration")